Skip to content

Add opt-in sourceMap option to emit .css.map files and sourceMappingURL comments alongside compiled CSS#5806

Open
cmalonzo wants to merge 2 commits into
microsoft:mainfrom
cmalonzo:heft-sass-plugin/sourceMaps
Open

Add opt-in sourceMap option to emit .css.map files and sourceMappingURL comments alongside compiled CSS#5806
cmalonzo wants to merge 2 commits into
microsoft:mainfrom
cmalonzo:heft-sass-plugin/sourceMaps

Conversation

@cmalonzo
Copy link
Copy Markdown
Contributor

@cmalonzo cmalonzo commented May 19, 2026

Summary

Fixes sp-dev-docs#10831 — SPFx 1.23 dev builds lost CSS/SCSS source-map support because @rushstack/heft-sass-plugin never passed sourceMap: true to sass-embedded, so no .css.map files were produced and DevTools "Sources" pointed at intermediate compiled files instead of the original .scss.

Changes

src/SassProcessor.ts

  • Added sourceMap?: boolean to ISassProcessorOptions
  • When sourceMap: true, passes sourceMap: true and sourceMapIncludeSources: true to compileStringAsync so result.sourceMap is populated
  • For each CSS output folder: writes a <file>.css.map alongside the .css, with sources[] rewritten from internal heft: URLs to paths relative to the map file's directory (what source-map-loader expects); appends /*# sourceMappingURL=<basename>.css.map */ to the .css
  • Default is false — no behavior change for existing consumers

src/SassPlugin.ts

  • Added sourceMap?: boolean to ISassConfigurationJson and threads it through to ISassProcessorOptions

src/schemas/heft-sass-plugin.schema.json

  • Added "sourceMap" boolean property

src/test/SassProcessor.test.ts

  • Added three tests under describe('sourceMap option'):
    • sourceMap: true emits a .css.map and a sourceMappingURL comment; map is valid JSON v3 with non-empty mappings and sources[0] resolving to the input .scss
    • Default (sourceMap unset) produces no .css.map and no sourceMappingURL comment, confirming byte-identical output to pre-change behavior
    • sourceMap: true + doNotTrimOriginalFileExtension: true uses the correct foo.scss.css.map filename in both the written file and the sourceMappingURL comment

Downstream contract

The plugin's only obligation to consumers is:

  • A parseable v3 source map at <file>.css.map
  • A /*# sourceMappingURL=<basename>.css.map */ comment in the emitted .css
  • sources[] entries that resolve (relative to the .css.map) to the original .scss on disk

@cmalonzo
Copy link
Copy Markdown
Contributor Author

Blocker for SharePoint/sp-dev-docs#10831

{
"changes": [
{
"comment": "Add opt-in sourceMap option to emit .css.map files and sourceMappingURL comments alongside compiled CSS",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"comment": "Add opt-in sourceMap option to emit .css.map files and sourceMappingURL comments alongside compiled CSS",
"comment": "Add opt-in sourceMap option to emit `.css.map` files and `sourceMappingURL` comments alongside compiled CSS.",


"sourceMap": {
"type": "boolean",
"description": "If true, a .css.map source map file will be written next to each emitted .css file, and a sourceMappingURL comment will be appended to the .css. Defaults to false."
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"description": "If true, a .css.map source map file will be written next to each emitted .css file, and a sourceMappingURL comment will be appended to the .css. Defaults to false."
"description": "If true, a `.css.map` source map file will be written next to each emitted `.css` file, and a `sourceMappingURL` comment will be appended to the `.css`. Defaults to `false`."

map.sources = map.sources.map((source) => {
const normalized: string = Path.convertToSlashes(source);
// sources[] are stored as paths relative to the .css.map output file, so they include a
// checkout-specific prefix like "../../../user/rushstack/heft-plugins/...". Strip everything
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't they be relative paths?

});
}
if (map.sourcesContent) {
map.sourcesContent = map.sourcesContent.map((entry) => entry.replace(/\r\n/g, '\n'));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
map.sourcesContent = map.sourcesContent.map((entry) => entry.replace(/\r\n/g, '\n'));
map.sourcesContent = map.sourcesContent.map(Text.convertToLf);

(Text comes from node-core-library)

// cssFilename is identical across all output folders.
const cssMapBasename: string | undefined =
sourceMap && result.sourceMap ? `${cssFilename}.map` : undefined;
const finalCss: string = cssMapBasename ? `${css}\n/*# sourceMappingURL=${cssMapBasename} */\n` : css;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const finalCss: string = cssMapBasename ? `${css}\n/*# sourceMappingURL=${cssMapBasename} */\n` : css;
const finalCss: string = cssMapBasename ? `${css}\n//# sourceMappingURL=${cssMapBasename}\n` : css;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Needs triage

Development

Successfully merging this pull request may close these issues.

2 participants